﻿using System;
using System.Linq;
using System.Net.Mail;
using System.Web;
using System.Web.Mvc;

using CreativeMinds.Security.Cryptography;

using mvcForum.Core;
using mvcForum.Core.Abstractions.Interfaces;
using mvcForum.Core.Specifications;
using mvcForum.Web.Controllers;
using mvcForum.Web.Filters;
using mvcForum.Web.Helpers;
using mvcForum.Web.Interfaces;
using mvcForum.Web.Services;
using mvcForum.Web.ViewModels;
using mvcForum.Core.Interfaces.Services;
using mvcForum.Core.Interfaces.Data;

namespace mvcForum.Web.Areas.Forum.Controllers {

	[ConfigurationControlled(ConfigurationArea.AccountController, "")]
	public class AccountController : ThemedForumBaseController {
		private readonly IAuthenticationService authService;
		private readonly IMembershipService membershipService;
		private readonly IRepository<BannedIP> bipRepo;
		private readonly IConfiguration config;
		private readonly IMailService mailService;
		private readonly ILogger logger;

		public AccountController(IContext context,
								IWebUserProvider userProvider,
								IAuthenticationService authService,
								IMembershipService membershipService,
								IRepository<BannedIP> bipRepo,
								IConfiguration config,
								IMailService mailService,
								ILogger logger)
			: base(userProvider, context) {

			this.authService = authService;
			this.membershipService = membershipService;
			this.config = config;
			this.bipRepo = bipRepo;
			this.mailService = mailService;
			this.logger = logger;
		}

		public ActionResult LogOff() {
			this.authService.SignOut();
			return Redirect(this.config.RedirectUrlLoggedOut);
		}

		[HttpGet]
		[ConfigurationControlled(ConfigurationArea.AccountController, "SignUp")]
		public ActionResult Register() {
			if (!this.config.UseForumAccountController || !this.config.AllowLocalUsers || !this.config.AllowSignUp) {
				return new HttpNotFoundResult();
			}
			return View(new RegisterModel {
				RequireRulesAccept = this.config.RequireRulesAccept,
				SignUpRules = this.config.SignUpRules
			});
		}

		[HttpPost]
		[ConfigurationControlled(ConfigurationArea.AccountController, "SignUp")]
		public ActionResult Register(RegisterModel model) {
			if (!this.config.UseForumAccountController || !this.config.AllowLocalUsers || !this.config.AllowSignUp) {
				return new HttpNotFoundResult();
			}

			if (ModelState.IsValid) {
				if ((this.config.RequireRulesAccept && model.RulesAccepted) || !this.config.RequireRulesAccept) {
					String errorMessage;
					// Attempt to register the user
					if (this.membershipService.CreateAccount(model.Username, model.Password, model.EmailAddress, out errorMessage)) {
						if (this.config.RequireEmailValidation) {
							IAccount account = this.membershipService.GetAccountByName(model.Username);

							String code = String.Format("{1}#|#{0}#|#{2}", account.AccountName, account.EmailAddress, account.ProviderUserKey);

							PrivatePrivateCrypto crypt = new PrivatePrivateCrypto();
							// hm.....
							crypt.Phrase = this.config.DefaultTimezone;

							String siteUrl = this.config.SiteURL;
							if (!siteUrl.EndsWith("/")) {
								siteUrl += "/";
							}
							String url = String.Format("{0}forum/account/activate?code={1}", siteUrl, HttpUtility.UrlEncode(crypt.Encrypt(code)));

							this.mailService.Send(
								new MailAddress(this.config.RobotEmailAddress, this.config.RobotName),
								new MailAddress[] { new MailAddress(model.EmailAddress, model.Username) }.ToList(),
								this.config.ValidationSubject,
								this.config.ValidationBody
									.Replace("{Email}", model.EmailAddress)
									.Replace("{Password}", model.Password)
									.Replace("{Url}", url));

							TempData.Add("Status", ForumHelper.GetString<mvcForum.Web.ForumConfigurator>("Register.EmailActivation"));
						}
						else {
							IRepository<ForumUser> userRepo = this.Context.GetRepository<ForumUser>();
							ForumUser user = userRepo.ReadOne(new ForumUserSpecifications.SpecificEmailAddress(model.EmailAddress));
							user.Active = true;
							this.Context.SaveChanges();

							TempData.Add("Status", ForumHelper.GetString<mvcForum.Web.ForumConfigurator>("Register.AccountReady"));
						}

						return Redirect(this.config.RedirectUrlRegistrered);
					}
					else {
						ModelState.AddModelError("", errorMessage);
					}
				}
				else {
					ModelState.AddModelError("RulesAccepted", ForumHelper.GetString<mvcForum.Web.ForumConfigurator>("Register.RulesMustBeAccepted"));
				}
			}

			// If we got this far, something failed, redisplay form
			model.RequireRulesAccept = this.config.RequireRulesAccept;
			model.SignUpRules = this.config.SignUpRules;
			return View(model);
		}

		[HttpGet]
		[ConfigurationControlled(ConfigurationArea.AccountController, "LocalOrOpenAuth")]
		public ActionResult LogOn() {
			if (!this.config.UseForumAccountController || (!this.config.AllowLocalUsers && !this.config.AllowOpenAuthUsers)) {
				return new HttpNotFoundResult();
			}
			// Get any bans tied to this user's IP address.
			BannedIP banned = this.bipRepo.ReadOne(new BannedIPSpecifications.SpecificIP(this.Request.UserHostAddress));
			// Has the visitors IP Address been banned?
			if (banned != null) {
				// Yes, let's tell him!
				TempData.Add("Reason", ForumHelper.GetString<mvcForum.Web.ForumConfigurator>("NoAccess.BannedIP", new { IPAddress = Request.UserHostAddress }));
				// Off to the "no access" page.
				return RedirectToRoute("NoAccess");
			}

			return View(new LogOnModel { AllowLocalUsers = this.config.AllowLocalUsers, AllowSignUp = this.config.AllowSignUp, AllowOpenAuthUsers = this.config.AllowOpenAuthUsers });
		}

		[HttpPost]
		[ConfigurationControlled(ConfigurationArea.AccountController, "Local")]
		public ActionResult LogOn(LogOnModel model, String returnUrl) {
			if (!this.config.UseForumAccountController || !this.config.AllowLocalUsers) {
				return new HttpNotFoundResult();
			}

			if (ModelState.IsValid) {
				BannedIP banned = this.bipRepo.ReadOne(new BannedIPSpecifications.SpecificIP(this.Request.UserHostAddress));
				// Found any?
				if (banned != null) {
					// The IP address is banned!
					TempData.Add("Reason", ForumHelper.GetString<mvcForum.Web.ForumConfigurator>("NoAccess.BannedIP"));
					// Off to the "no access" page.
					return RedirectToRoute("NoAccess");
				}

				String username = this.membershipService.GetAccountNameByEmailAddress(model.EmailAddress);
				// Let's try to validate the user.
				if (!String.IsNullOrWhiteSpace(username) && this.membershipService.ValidateAccount(username, model.Password)) {
					// Success, let's sign the user in.
					ForumUser u = this.ForumUserRepository.ReadOne(new ForumUserSpecifications.SpecificUsername(username));
					if (u != null) {
						if (!u.ExternalAccount) {
							this.authService.SignIn(this.membershipService.GetAccountByName(username), model.RememberMe);

							if (Url.IsLocalUrl(returnUrl)) {
								return Redirect(returnUrl);
							}
							else {
								return Redirect(this.config.RedirectUrlLoggedIn);
							}
						}
						else {
							// External (OpenAuth account), should not log on through this action!!
							// TODO: Get error message form resources
							ModelState.AddModelError(String.Empty, "The account is an external account, please log on using the right provider.");
						}
					}
				}
				else {
					// Couldn't validate the user!
					// TODO: Get error message form resources
					ModelState.AddModelError(String.Empty, "The user name or password provided is incorrect.");
				}
			}

			model.AllowLocalUsers = this.config.AllowLocalUsers;
			model.AllowSignUp = this.config.AllowSignUp;
			model.AllowOpenAuthUsers = this.config.AllowOpenAuthUsers;
			return View(model);
		}

		//[HttpGet]
		//[ConfigurationControlled(ConfigurationArea.AccountController, "Local")]
		//public ActionResult ForgottenPassword() {
		//    return View();
		//}

		[HttpPost]
		[ConfigurationControlled(ConfigurationArea.AccountController, "Local")]
		public ActionResult ForgottenPassword(ForgottenPassword model) {
			if (ModelState.IsValid) {
				IAccount account = this.membershipService.GetAccountByEmailAddress(model.EmailAddress);
				String newPassword = this.membershipService.ResetPassword(account);
				this.membershipService.UpdateAccount(account);

				this.mailService.Send(
					new MailAddress(this.config.RobotEmailAddress, this.config.RobotName),
					new MailAddress[] { new MailAddress(account.EmailAddress, account.AccountName) }.ToList(),
					this.config.ForgottenPasswordSubject,
					this.config.ForgottenPasswordBody
						.Replace("{Email}", model.EmailAddress)
						.Replace("{Password}", newPassword));

				TempData.Add("ForgottenStatus", ForumHelper.GetString("PasswordChanged", null, "mvcForum.Web.ForgottenPassword"));
			}
			//return this.LogOn();
			return RedirectToAction("logon");
		}

		public ActionResult External() {
			return View();
		}

		public ActionResult Success() {
			return View();
		}

		[ConfigurationControlled(ConfigurationArea.AccountController, "SignUp")]
		public ActionResult Activate(String code) {
			Boolean success = false;
			if (!String.IsNullOrWhiteSpace(code)) {
				PrivatePrivateCrypto crypt = new PrivatePrivateCrypto();
				// Hm...
				crypt.Phrase = this.config.DefaultTimezone;
				logger.Log(EventType.Debug, String.Format("Phrase set {0}", this.config.DefaultTimezone));
				try {
					String decrypted = crypt.Decrypt(code);
					logger.Log(EventType.Debug, String.Format("Decrypted, {0}", decrypted));

					String[] parts = decrypted.Split(new String[] { "#|#" }, StringSplitOptions.RemoveEmptyEntries);
					if (parts.Length == 3) {
						IAccount account = this.membershipService.GetAccountByName(parts[1]);
						if (account != null && account.EmailAddress == parts[0] && account.ProviderUserKey.ToString() == parts[2]) {
							account.IsApproved = true;
							this.membershipService.UpdateAccount(account);
							success = true;

							ForumUser fu = this.ForumUserRepository.ReadOne(new ForumUserSpecifications.SpecificProviderUserKey(account.ProviderUserKey.ToString()));
							fu.Active = true;
							this.Context.SaveChanges();
						}
					}
				}
				catch (Exception ex) {
					this.logger.Log(EventType.Error, "Activate failed", ex);
				}
			}

			return View(success);
		}
	}
}